package sqcon.util;

import java.util.ArrayList;
import java.util.NoSuchElementException;

//TODO implement this class make it have the most features of 
//java.util.StringTokenizer but have more functionalities like bracket 
//handling. Use the code from StringTokenizer
public class StringTokenParser {

	private final String str;
	private final int len;
	private String delim;
	private int pos;
	
	private String[] specialTokens;
	
	private final boolean separateParenthesisToken;
	
	/**
	 * Default StringTokenParser constructor, the default delim is " \t\n\r\f".
	 * The parser does not separate parenthesis token by default
	 * @param str
	 */
	public StringTokenParser(String str) {
		this(str, " \t\n\r\f");
	}

	/**
	 * Construct a new StringTokenParser object, by given string and delim. 
	 * Doesn not separate parenthesis token by default
	 * @param str
	 * @param delim
	 */
	public StringTokenParser(String str, String delim) {
		this(str, delim, false);
	}
	
	/**
	 * Construct a new StringTokenParser object from a given string
	 * @param str
	 * @param separate, if true separate parenthesis block as a string token
	 */
	public StringTokenParser(String str, boolean separate){
		this(str, " \t\n\r\f", separate);
	}
	
	public StringTokenParser(String str, String delim, boolean separate){
		this(str, delim, null, separate);
	}
	
	/**
	 * The array of specialTokens must be ordered with complex ones first, this
	 * means that if a special token A is a subset of another special token B,
	 * B must be ordered before A 
	 * @param str
	 * @param specialTokens
	 * @param separate
	 */
	public StringTokenParser(String str, String[] specialTokens, 
			boolean separate){
		this(str, " \t\n\r\f", specialTokens, separate);
	}
	
	/**
	 * The array of specialTokens must be ordered with complex ones first, this
	 * means that if a special token A is a subset of another special token B,
	 * B must be ordered before A 
	 * @param str
	 * @param delim
	 * @param specialTokens
	 */
	public StringTokenParser(String str, String delim, String[] specialTokens){
		this(str, delim, specialTokens, false);
	}
	
	/**
	 * The array of specialTokens must be ordered with complex ones first, this
	 * means that if a special token A is a subset of another special token B,
	 * B must be ordered before A 
	 * @param str
	 * @param delim
	 * @param specialTokens
	 * @param separateParenthesisToken
	 */
	public StringTokenParser(String str, String delim, String[] specialTokens,
			boolean separateParenthesisToken){
		len = str.length();
		this.str =str;
		this.delim = delim.toString();
		this.pos = 0;
		this.specialTokens = specialTokens;
		this.separateParenthesisToken = separateParenthesisToken;
	}
	
	public boolean hasMoreTokens() {
		while (pos < len && delim.indexOf(str.charAt(pos)) >= 0) {
			pos++;
		}
		return pos < len;
	}
	
	/**
	 * same as StringTokenizer.nextToken however, treats brackets and the 
	 * content within as a separate token if separateParenthesisToken is true
	 * @return
	 */
	//TODO ugly, rewrite
	public String nextToken() throws NoSuchElementException, 
			StringParsingException{
		String spTok = null;
		
		//skip the delimiters here
		if (pos < len && delim.indexOf(str.charAt(pos)) >= 0) {
			while (++pos < len && delim.indexOf(str.charAt(pos)) >= 0);
		}
		
		//if not at the end of the string
		if(pos < len){
			int start = pos;
			
			if(separateParenthesisToken){
				if(str.charAt(pos) == '('){
					skipParenthesis();
				}else if(considerSpecialTokens() && 
						(spTok = isSpecialToken()) != null){
					pos = pos + spTok.length();
					return spTok;
				}else{
					pos--;
					while(++pos < len && delim.indexOf(str.charAt(pos)) < 0){
						if(str.charAt(pos) == '('){
							break;
						}else if(str.charAt(pos) == ')'){
							throw new StringParsingException("Unexpected " +
									"right parenthesis.");
						}else if(considerSpecialTokens() && 
								isSpecialToken() != null){
							break;
						}
					}
				}
			}else{
				if(considerSpecialTokens() && 
						(spTok = isSpecialToken()) != null){
					pos = pos + spTok.length();
					return spTok;
				}
				pos--;
				while(++pos < len && delim.indexOf(str.charAt(pos)) < 0){
					if(considerSpecialTokens() && isSpecialToken() != null){
						break;
					}
				}
			}
			return str.substring(start, pos);
		}
		
		throw new NoSuchElementException();
	}
	
	public String getRest(){
		return str.substring(pos);
	}
	
	public boolean considerSpecialTokens(){
		return specialTokens != null && specialTokens.length != 0;
	}
	
	/**
	 * Returns an array of string each element is leaded by the specified 
	 * keyword, returns null if the given target string does not contain any
	 * words.
	 * @param targetStr
	 * @param keyWords
	 * @return
	 * @throws StringParsingException
	 */
	public static String[] segmentByKeywords(String targetStr, 
			String[] keyWords) throws StringParsingException{
		StringTokenParser stp = new StringTokenParser(targetStr, true);
		
		ArrayList<String> retStrArray = new ArrayList<String>();
		String segment = "";
		String word = "";
		String key = "";
		
		if(stp.hasMoreTokens()){
			key = stp.nextToken();
			if(!isKeyWord(key, keyWords)){
				throw new StringParsingException("Target string does not " +
						"start with keyword...");
			}
			segment = key;
		}else{
			return null;
		}
		
		while(stp.hasMoreTokens()){
			word = stp.nextToken();
			if(isKeyWord(word, keyWords)){
				key = word;
				retStrArray.add(segment);
				segment = key;
			}else{
				segment = segment + " " + word;
			}
		}
		retStrArray.add(segment);
		
		return retStrArray.toArray(new String[retStrArray.size()]);
	}
	
	private static boolean isKeyWord(String str, String[] keyWords){
		for(String key : keyWords){
			if(str.equalsIgnoreCase(key)){
				return true;
			}
		}
		return false;
	}
	
	/**
	 * this method assumes the current pos is at '('
	 */
	private void skipParenthesis() throws StringParsingException{
		while(++pos < len){
			if(str.charAt(pos) == '('){
				skipParenthesis();
			}else if(str.charAt(pos) == ')'){
				pos++;
				return;
			}
		}
		throw new StringParsingException("Parenthesis error.");
	}

	private String isSpecialToken(){
		boolean ret = true;
		
		for(String spToken : specialTokens){
			int end = 0;
			ret = true;
			for(end = 0; end < spToken.length(); end++){
				if(pos + end >= len || 
						str.charAt(pos + end) != spToken.charAt(end)){
					ret = false;
				}
			}
			if(ret){
				return str.substring(pos, pos + end);
			}
		}
			
		return null;
	}
}
